package com.simon.mctm.token;


import com.simon.mctm.biz.sys.bean.User;
import com.simon.mctm.exception.OperationFailedException;
import com.simon.mctm.redis.RedisUtil;

import com.simon.mctm.utils.Utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * token工具类
 * 结合redis简化截止时间的操作流程
 * jwt生成的token不设置过期时间，过期时间放在redis中管理：
 * 1.存储redis的时候，设置时长，过期后获取不到token，token过期
 * 2.用户验证token成功后，重新延长时长
 * 3.用户修改密码后，token验证失败
 * 4.存储在redis中的为auth对象，包含token和随机数，key为username
 * @author Jiajie.Mao
 *
 */
@Component
public class TokenUtil {

    private static final long serialVersionUID = -3301605591108950415L;

    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    private static final String CLAIM_KEY_RANDOMKEY = "random";

    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    
    @Autowired
    private RedisUtil redisUtil;
    
    
    /**
     * 创建token，并存放在redis中
     * @param username
     * @return
     */
    public Auth createToken(String username) {    	
    	//生成随机数
      	String randomKey = Utils.getRandomString(6);
      	
        //使用jwt作为源token
        String token = generateToken(username,randomKey);
        
        //构建令牌
        StringBuilder sb = new StringBuilder(tokenHead);
        String authorization = sb.append(" ").append(token).toString();
        
        Auth auth = new Auth(authorization,token,randomKey);
        //设置值到redis中
        redisUtil.setRedis(username, token);
        return auth;
    }
    
    /**
     * 根据用户名从redis中获取token信息
     * @param username
     * @return
     */
    public String getToken(String username) {
    	return redisUtil.getRedis(username);
//    	String[] auths = auth.split("-");
//    	return auths[0];
    }
    
    /**
     * 根据用户名从redis中获取随机数信息
     * @param username
     * @return
     */
    public String getRandomKey(String username) {
    	String auth = redisUtil.getRedis(username);
    	String[] auths = auth.split("-");
    	return auths[1];
    }
    
    /**
     * 验证token
     * @param token
     * @return
     */
    public boolean checkToken(String token,User user) {
        if (token == null) {
            return false;
        }
        String username = getUsernameFromToken(token);
        String rToken = getToken(username);        
        //判断传输的token是否与redis中存放的token一致
        if (rToken == null || !rToken.equals(token)) {
            return false;
        }
        Date created = getCreatedDateFromToken(token);
        //判断token发放之后，用户是否修改过密码
        if (isCreatedBeforeLastPasswordReset(created, user.getLastpasswordreset())) {
			return false;
		}
        //如果验证成功，说明此用户进行了一次有效操作，延长token的过期时间
        redisUtil.expireRedis(username);
        
        return true;
    }
    
    public Boolean deleteToken(String token) {
        try {
            String username = getUsernameFromToken(token);
            return redisUtil.deleteRedis(username);
        }catch (RuntimeException ex){
            throw  new OperationFailedException(ex.getLocalizedMessage());
        }

    }
    
    
    /**
     * 根据用户名删除token信息
     * @param username
     */
    public Boolean deleteTokenByKey(String username) {
        return redisUtil.deleteRedis(username);
    }
    
    /**
     * 从token中获取用户名
     * @param token
     * @return
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }
    
    /**
     * 从token中获取创建时间
     * @param token
     * @return
     */
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        } catch (Exception e) {
            created = null;
        }
        return created;
    }
    
    /**
     * 获取随机数
     * @param token
     * @return
     */
    public String getRandomKeyFromToken(String token) {
        String randomKey;
        try {
            final Claims claims = getClaimsFromToken(token);
            randomKey =  (String) claims.get(CLAIM_KEY_RANDOMKEY);
        } catch (Exception e) {
        	randomKey = null;
        }
        return randomKey;
    }
    

    
    /**
     * 从token中获取内容
     * @param token
     * @return
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    
    /**
     * 验证token是否过期
     * @param token
     * @return
     */
    private Boolean isTokenExpired(String token) {
    	
    	if (token == null) {
            return false;
        }
        String username = getUsernameFromToken(token);
        String auth = redisUtil.getRedis(username);
        if("".equals(auth) || auth == null) {
        	return false;
        }
        
        return true;
    }
    
    /**
     * 验证token是否是用户变更密码之前创建
     * @param created
     * @param lastPasswordReset
     * @return
     */
    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }
    
    /**
     * 创建token的内容，填充用户名和创建时间
     * @return
     */
    public String generateToken(String username,String randomKey) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, username);
        claims.put(CLAIM_KEY_CREATED, new Date());
        //设置随机数
        claims.put(CLAIM_KEY_RANDOMKEY, randomKey);
        return generateToken(claims);
    }

    String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

}
